%{
#include "ast.h"
#include "errmsg.h"
#include "symbol.h"
#include "parser.h"
#include "utils.h"

static int _char_pos = 1;

int yywrap(void);

#define ADJ \
do \
{ \
    em_tok_pos = yylval.pos = _char_pos; \
    _char_pos += yyleng; \
} \
while (0)

#define INIT_LEN 32
static string_t _str_buf;
static char _chr_buf;
static int _str_len;
static int _str_cap;
static void init_buf(void);
static void append_char(char ch);

%}

%option noinput
%option nounput

%x STRING CHAR CHAR_END

%%

"//"(.)*                { ADJ; /*printf("comment\n");*/ }
[ \t\f\v\r]             { ADJ; /*printf("white space\n");*/ }
\n                      { ADJ; em_newline(); /*printf("new line\n");*/ }

";"                     { ADJ; return SEMICOLON; }
":"											{ ADJ; return COLON; }
"("                     { ADJ; return LPARAN; }
")"                     { ADJ; return RPARAN; }
"{"                     { ADJ; return LBRACE; }
"}"                     { ADJ; return RBRACE; }
"["                     { ADJ; return LBRACK; }
"]"                     { ADJ; return RBRACK; }
"."                     { ADJ; return DOT; }
","                     { ADJ; return COMMA; }
"="                     { ADJ; return ASSIGN; }
"&&"                    { ADJ; return AND; }
"||"                    { ADJ; return OR; }
"<"                     { ADJ; return LT; }
">"                     { ADJ; return GT; }
"<="                    { ADJ; return LE; }
">="                    { ADJ; return GE; }
"=="                    { ADJ; return EQ; }
"!="                    { ADJ; return NEQ; }
"!"                     { ADJ; return NOT; }
"+"                     { ADJ; return PLUS; }
"-"                     { ADJ; return MINUS; }
"*"                     { ADJ; return TIMES; }
"/"                     { ADJ; return DIVIDE; }


class                   { ADJ; return CLASS; }
call										{ ADJ; return CALL; }
length									{ ADJ; return LENGTH; }
void										{ ADJ; return VOID; }
int                     { ADJ; return INT; }
float                   { ADJ; return FLOAT; }
char                    { ADJ; return CHAR; }
string                  { ADJ; return STRING; }
boolean                 { ADJ; return BOOLEAN; }
if                      { ADJ; return IF; }
else                    { ADJ; return ELSE; }
while                   { ADJ; return WHILE; }
return                  { ADJ; return RETURN; }
this                    { ADJ; return THIS; }
new                     { ADJ; return NEW; }
true                    { ADJ; yylval.bval = true; return BOOLEAN_LIT; }
false                   { ADJ; yylval.bval = false; return BOOLEAN_LIT; }

[0-9]+                  { ADJ; yylval.ival = atoi(yytext); return INT_LIT; }
[0-9]+"."[0-9]+         { ADJ; yylval.fval = atof(yytext); return FLOAT_LIT; }
[_a-zA-Z][_a-zA-Z0-9]*  { ADJ; yylval.str = string(yytext); return IDF; }

\"                      { ADJ; init_buf(); BEGIN(STRING); }
<STRING>\"              {
    ADJ;
    BEGIN(INITIAL);
    yylval.str = _str_buf;
    return STRING_LIT;
}
<STRING>\n              {
		ADJ;
		em_newline();
		em_error(em_tok_pos, "error: missing terminating \" character");
    BEGIN(INITIAL);
}
<STRING>\\[0-9]{3}      {
    int result;

    ADJ;
    sscanf(yytext + 1, "%d", &result);
    if (result > 0xFF) {
				em_error(em_tok_pos, "error: unknown escape sequence: '%s'", yytext);
    		BEGIN(INITIAL);
		}
    append_char(result);
}
<STRING>\\n             { ADJ; append_char('\n'); }
<STRING>\\t             { ADJ; append_char('\t'); }
<STRING>\\\"            { ADJ; append_char('\"'); }
<STRING>\\\'            { ADJ; append_char('\''); }
<STRING>\\\\            { ADJ; append_char('\\'); }
<STRING>\\\n						{ ADJ; em_newline(); }
<STRING>\\.        			{
		ADJ;
		em_error(em_tok_pos, "error: unknown escape sequence: '%s'", yytext);
    BEGIN(INITIAL);
}
<STRING>[^\\\n\"]+      {
    char *p = yytext;

    ADJ;
    while (*p)
        append_char(*p++);
}



"'"                     { ADJ; _chr_buf = '\0'; BEGIN(CHAR); }
<CHAR>"'"               {
    ADJ;
    em_error(em_tok_pos, "error: empty character constant");
    BEGIN(INITIAL);
}

<CHAR>\n                {
    ADJ;
    em_newline();
    em_error(em_tok_pos, "error: missing terminating ' character");
    BEGIN(INITIAL);
}
<CHAR>\\[0-9]{3}     		{
    int result;

    ADJ;
    sscanf(yytext + 1, "%d", &result);
    if (result > 0xFF) {
				yytext[4] = '\0'; 		// tricky hack: remove last ' character
				em_error(em_tok_pos, "error: unknown escape sequence: '%s'", yytext);
    		BEGIN(INITIAL);
		}
    else {
			BEGIN(CHAR_END);
			_chr_buf = result;
    }
}
<CHAR>\\n             {
    ADJ;
    BEGIN(CHAR_END);
		_chr_buf = '\n';
}
<CHAR>\\t             {
    ADJ;
    BEGIN(CHAR_END);
		_chr_buf = '\t';
}
<CHAR>\\\"             {
    ADJ;
    BEGIN(CHAR_END);
		_chr_buf = '\"';
}
<CHAR>\\\'             {
    ADJ;
    BEGIN(CHAR_END);
		_chr_buf = '\'';
}
<CHAR>\\\\             {
    ADJ;
    BEGIN(CHAR_END);
		_chr_buf = '\\';
}
<CHAR>\\\n             { ADJ; em_newline(); }
<CHAR>\\.   					 {
		ADJ;
		em_error(em_tok_pos, "error: unknown escape sequence: '%s'", yytext);
		BEGIN(INITIAL);
}
<CHAR>[^\\\n]          {
    ADJ;
    BEGIN(CHAR_END);
		_chr_buf = *yytext;
}
<CHAR_END>"'" 				 {
		ADJ;
		BEGIN(INITIAL);
		yylval.chr = _chr_buf;
    return CHAR_LIT;
}
<CHAR_END>[^\']+"'"   {
    ADJ;
    BEGIN(INITIAL);
		em_error(em_tok_pos, "error: multi-character character constant");
}

.                      { ADJ; em_error(em_tok_pos, "error: illegal token '%s'", yytext); }

%%

int yywrap(void)
{
    _char_pos = 1;
    return 1;
}

static void init_buf(void)
{
    _str_buf = checked_malloc(INIT_LEN);
    _str_buf[0] = 0;
    _str_len = 0;
    _str_cap = INIT_LEN;
}

static void append_char(char ch)
{
    if (++_str_len == _str_cap)
    {
        char *p;

        _str_cap *= 2;
        p = checked_malloc(_str_cap);
        memcpy(p, _str_buf, _str_len);
        free(_str_buf);
        _str_buf = p;
    }
    _str_buf[_str_len - 1] = ch;
    _str_buf[_str_len] = 0;
}

void scan(string_t filename)
{
    em_reset(filename);
    int token;
    
    string_t TOKEN_NAME[] = {
		    "CLASS", "LBRACE", "RBRACE", "SEMICOLON", "LPARAN",
		    "RPARAN", "RETURN", "COMMA", "LBARCK", "RBRACK",
		    "IF", "ELSE", "WHILE", "ASSIGN", "NOT",
		    "DOT", "THIS", "NEW", "INT", "CHAR",
		    "FLOAT", "STRING", "BOOLEAN", "AND", "OR",
		    "LT", "GT", "LE", "GE", "EQ",
		    "NEQ", "PLUS", "MINUS", "TIMES", "DIVIDE",
		    "COLON", "VOID", "CALL", "LENGTH", "INT_LIT",
		    "FLOAT_LIT", "IDF", "STRING_LIT", "CHAR_LIT", "BOOLEAN_LIT"
		};
		
    while ((token = yylex())) {
				if (token == BOOLEAN_LIT)
					printf("%s: %d\n", TOKEN_NAME[token-CLASS], yylval.bval);
				else if (token == INT_LIT)
					printf("%s: %d\n", TOKEN_NAME[token-CLASS], yylval.ival);
				else if (token == FLOAT_LIT)
					printf("%s: %f\n", TOKEN_NAME[token-CLASS], yylval.fval);
				else if (token == IDF)
					printf("%s: %s\n", TOKEN_NAME[token-CLASS], yylval.str);
				else if (token == CHAR_LIT)
					printf("%s: '%c'\n", TOKEN_NAME[token-CLASS], yylval.chr);
				else if (token == STRING_LIT)
					printf("%s: \"%s\"\n", TOKEN_NAME[token-CLASS], yylval.str);
				else
					printf("%s\n", TOKEN_NAME[token-CLASS]);
		}
}
